﻿using System.IO.Ports;
using System.Text;
using System.Windows;
using MG.Communication;
using MG.Communication.Core;
using MG.Communication.ModBus;
using MG.Repository;
using MG.SmartHMI.Common;
using MG.SmartHMI.Entity.DAO;
using MG.SmartHMI.Model.Common;
using MG.Util;
using MG.Util.Log;
using SqlSugar;
using ModbusInfo = MG.SmartHMI.Entity.DAO.ModbusInfo;

namespace MG.SmartHMI.Domain
{
    internal class ModbusCtrl
    {
        #region Parm

        private IReadWriteNet ModbusClient { get; set; }

        private static ModbusInfo ModbusInf { get; set; }
        private static ModbusRtu ModRtu { get; set; }
        private ModbusTcpNet ModTcp { get; set; }

        private bool isConnected { get; set; } = false;

        public IByteTransform byteTransform { get; private set; }

        public int StartAddress { get; set; }

        public int Len { get; set; }

        public int BoolStartAddress { get; set; }

        public int BoolLen { get; set; }

        #endregion Parm

        #region construct

        private static readonly Lazy<ModbusCtrl> instance = new Lazy<ModbusCtrl>(() => new ModbusCtrl());
        public static ModbusCtrl Instance => instance.Value;

        private ModbusCtrl()
        {
            Start();
            // Initialize any resources or settings needed for the process engine
        }

        #endregion construct

        #region interface

        public void Start()
        {
            Update();
            try
            {
                if (ModbusInf.EnableRtu)
                {
                    ModRtu = new ModbusRtu();
                    ModRtu.SerialPortInni(sp =>
                    {
                        sp.PortName = ModbusInf.PortName;
                        sp.BaudRate = ModbusInf.BaudRate;
                        sp.DataBits = ModbusInf.DataBits;
                        sp.StopBits = (StopBits)ModbusInf.StopBits;
                        sp.Parity = (Parity)ModbusInf.Parity;
                    });
                    ModRtu.DataFormat = (Communication.Core.DataFormat)ModbusInf.DataFormat;
                    ModRtu.Station = byte.Parse(ModbusInf.Station);
                    ModRtu.IsStringReverse = ModbusInf.StrResever;
                    ModRtu.AddressStartWithZero = ModbusInf.FirstStrFrom0;

                    ModRtu.Open();
                    isConnected = ModRtu.IsOpen();
                    if (isConnected)
                    {
                        byteTransform = ModRtu.ByteTransform;
                        ModbusClient = ModRtu;
                    }
                }
                else
                {
                    ModTcp = new ModbusTcpNet(ModbusInf.IP, ModbusInf.Port);
                    ModTcp.DataFormat = (Communication.Core.DataFormat)ModbusInf.DataFormat;
                    ModTcp.Station = byte.Parse(ModbusInf.Station);
                    ModTcp.IsStringReverse = ModbusInf.StrResever;
                    ModTcp.AddressStartWithZero = ModbusInf.FirstStrFrom0;
                    isConnected = ModTcp.ConnectServer().IsSuccess;
                    if (isConnected)
                    {
                        byteTransform = ModTcp.ByteTransform;
                        ModbusClient = ModTcp;
                    }
                }
            }
            catch (Exception e1)
            {
                ModRtu?.Close();
                ModRtu?.Dispose();
                ModTcp?.ConnectClose();
                ModTcp?.Dispose();
                EventBus.Default.Log(new LogInfo($"Modbus连接失败 {e1.Message}", LogLevel.Error));
                MessageBox.Show($"Modbus连接失败 ", "提示", MessageBoxButton.OK, MessageBoxImage.Error);
            }

            if (!isConnected)
            {
                ModRtu?.Close();
                ModRtu?.Dispose();
                ModTcp?.ConnectClose();
                ModTcp?.Dispose();
                EventBus.Default.Log(new LogInfo($"Modbus连接失败 ", LogLevel.Error));

                App.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    MessageBox.Show($"Modbus连接失败 ", "提示", MessageBoxButton.OK, MessageBoxImage.Error);
                }));
            }
        }

        public void Close()
        {
            ModRtu?.Close();
            ModRtu?.Dispose();
            ModTcp?.ConnectClose();
            ModTcp?.Dispose();
        }

        public void Update()
        {
            ModbusInf = RepositoryService<ModbusInfo>.Instance.Query(t => !t.IsDelete).First();
        }

        public int TimeInterVal() => ModbusInf.TimeInterVal;

        public async ValueTask<OperateResult> WriteByTrigger(string address, EnumTriggerType trigger)
        {
            OperateResult res = new() { IsSuccess = false };
            switch (trigger)
            {
                case EnumTriggerType.HightLevel:
                    res = UnitWrite(EnumDataType.BOOL, address, "true");
                    break;

                case EnumTriggerType.LowLevel:
                    res = UnitWrite(EnumDataType.BOOL, address, "false");
                    break;

                case EnumTriggerType.UpEdge:
                    UnitWrite(EnumDataType.BOOL, address, "false");
                    await Task.Delay(50);
                    res = UnitWrite(EnumDataType.BOOL, address, "true");
                    break;

                case EnumTriggerType.DownEdge:
                    UnitWrite(EnumDataType.BOOL, address, "true");
                    await Task.Delay(50);
                    res = UnitWrite(EnumDataType.BOOL, address, "false");
                    break;

                case EnumTriggerType.Reversal:

                    bool rect = bool.Parse(UnitRead(EnumDataType.BOOL, address).Content);
                    res = UnitWrite(EnumDataType.BOOL, address, (!rect).ToString());
                    break;

                default:
                    break;
            }
            return res;
        }

        #region UnitRead/UnitWrite

        public OperateResult UnitWrite(EnumDataType datatype, string address, string values)
        {
            OperateResult res = new OperateResult() { IsSuccess = false };
            if (!isConnected || address.Equals("0")
                || (string.IsNullOrEmpty(address) || string.IsNullOrWhiteSpace(address))) return res;

            try
            {

                switch (datatype)
                {
                    case EnumDataType.BOOL:
                        var bflag = bool.TryParse(values, out bool boolValue);
                        if (!bflag)
                        {
                            res.Message = $"地址{address} 无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }

                        res = ModbusClient.Write(address, boolValue);
                        break;

                    case EnumDataType.SHORT:
                        var sflag = short.TryParse(values, out short shortvalue);
                        if (!sflag)
                        {
                            res.Message = $"地址{address} 无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, shortvalue);
                        break;

                    case EnumDataType.USHORT:
                        var usflag = ushort.TryParse(values, out ushort ushortvalue);
                        if (!usflag)
                        {
                            res.Message = $"地址{address} 无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, ushortvalue);
                        break;

                    case EnumDataType.INT:
                        var iflag = int.TryParse(values, out int intvalue);
                        if (!iflag)
                        {
                            res.Message = $"地址 {address}  无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, intvalue);
                        break;

                    case EnumDataType.UINT:

                        var uiflag = uint.TryParse(values, out uint uintvalue);
                        if (!uiflag)
                        {
                            res.Message = $"地址 {address}  无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, uintvalue);
                        break;

                    case EnumDataType.LONG:
                        var lflag = long.TryParse(values, out long lvalue);
                        if (!lflag)
                        {
                            res.Message = $"地址 {address}  无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, lvalue);
                        break;

                    case EnumDataType.ULONG:
                        var ulflag = ulong.TryParse(values, out ulong ulvalue);
                        if (!ulflag)
                        {
                            res.Message = $"地址 {address}  无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, ulvalue);
                        break;

                    case EnumDataType.FLOAT:
                        var fflag = float.TryParse(values, out float fvalue);
                        if (!fflag)
                        {
                            res.Message = $"地址 {address}  无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, fvalue);
                        break;

                    case EnumDataType.DOUBLE:
                        var dflag = double.TryParse(values, out double dvalue);
                        if (!dflag)
                        {
                            res.Message = $"地址 {address}  无效值{values}，无法转换为{datatype.GetDescrip()}类型";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, dvalue);
                        break;

                    case EnumDataType.STRING:
                        if (string.IsNullOrEmpty(values) || string.IsNullOrWhiteSpace(values))
                        {
                            res.Message = $"地址 {address}   值为空,无法写入";
                            EventBus.Default.Log(new LogInfo(res.Message, LogLevel.Error));
                            return res;
                        }
                        res = ModbusClient.Write(address, values);
                        break;

                    default:
                        break;
                }
            }
            catch (Exception e)
            {
                EventBus.Default.Log(new LogInfo(e.Message, LogLevel.Error));
                MessageBox.Show(e.Message, null, MessageBoxButton.OK, MessageBoxImage.Error);
            }

            return res;
        }

        public OperateResult<string> UnitRead(EnumDataType datatype, string address, int StrLen = 10)
        {
            OperateResult<string> res = new OperateResult<string>() { IsSuccess = false, Content = "0" };
            if (!isConnected || string.IsNullOrEmpty(address) || address.Equals("0")) return res;

            try
            {
                switch (datatype)
                {
                    case EnumDataType.BOOL:
                        var boolres = ModbusClient.ReadBool(address);
                        res.IsSuccess = boolres.IsSuccess;
                        res.Content = boolres.Content.ToString();
                        break;

                    case EnumDataType.SHORT:
                        var shortres = ModbusClient.ReadInt16(address);
                        res.IsSuccess = shortres.IsSuccess;
                        res.Content = shortres.Content.ToString();
                        break;

                    case EnumDataType.USHORT:
                        var ushortres = ModbusClient.ReadUInt16(address);
                        res.IsSuccess = ushortres.IsSuccess;
                        res.Content = ushortres.Content.ToString();
                        break;

                    case EnumDataType.INT:
                        var intres = ModbusClient.ReadInt32(address);
                        res.IsSuccess = intres.IsSuccess;
                        res.Content = intres.Content.ToString();
                        break;

                    case EnumDataType.UINT:
                        var uintres = ModbusClient.ReadUInt32(address);
                        res.IsSuccess = uintres.IsSuccess;
                        res.Content = uintres.Content.ToString();
                        break;

                    case EnumDataType.LONG:
                        var longres = ModbusClient.ReadInt64(address);
                        res.IsSuccess = longres.IsSuccess;
                        res.Content = longres.Content.ToString();
                        break;

                    case EnumDataType.ULONG:
                        var ulongres = ModbusClient.ReadUInt64(address);
                        res.IsSuccess = ulongres.IsSuccess;
                        res.Content = ulongres.Content.ToString();
                        break;

                    case EnumDataType.FLOAT:
                        var floatres = ModbusClient.ReadFloat(address);

                        res.IsSuccess = floatres.IsSuccess;
                        res.Content = floatres.Content.ToString();
                        break;

                    case EnumDataType.DOUBLE:
                        var doubleres = ModbusClient.ReadDouble(address);
                        res.IsSuccess = doubleres.IsSuccess;
                        res.Content = doubleres.Content.ToString();
                        break;

                    case EnumDataType.STRING:
                        var strres = ModbusClient.ReadString(address, (ushort)StrLen);
                        res.IsSuccess = strres.IsSuccess;
                        res.Content = strres.Content.ToString();
                        break;

                    default:
                        break;
                }
            }
            catch (Exception e)
            {
                EventBus.Default.Log(new LogInfo(e.Message, LogLevel.Error));
                MessageBox.Show(e.Message, null, MessageBoxButton.OK, MessageBoxImage.Error);
            }

            return res;
        }

        #endregion UnitRead/UnitWrite

        #region BatchRead

        public OperateResult<byte[]> BatchRead(string address, int len)
        {
            if (!isConnected) return new OperateResult<byte[]>() { IsSuccess = false, Content = null };
            return ModbusClient.Read(address, (ushort)len);
        }

        public OperateResult<byte[]> BatchRead()
        {
            if (!isConnected) return new OperateResult<byte[]>() { IsSuccess = false, Content = null };
            return ModbusClient.Read(StartAddress.ToString(), (ushort)Len);
        }

        public string ParseBatchData(ref byte[] data, EnumDataType datatype, int address, int lenstr = 10)
        {
            int index = 2 * (address - StartAddress);
            switch (datatype)
            {
                case EnumDataType.BOOL:
                    return byteTransform.TransBool(data, index).ToString();

                case EnumDataType.SHORT:
                    return byteTransform.TransUInt16(data, index).ToString();

                case EnumDataType.USHORT:
                    return byteTransform.TransUInt16(data, index).ToString();

                case EnumDataType.INT:
                    return byteTransform.TransInt32(data, index).ToString();

                case EnumDataType.UINT:
                    return byteTransform.TransUInt32(data, index).ToString();

                case EnumDataType.LONG:
                    return byteTransform.TransInt64(data, index).ToString();

                case EnumDataType.ULONG:
                    return byteTransform.TransUInt64(data, index).ToString();

                case EnumDataType.FLOAT:
                    return byteTransform.TransSingle(data, index).ToString();

                case EnumDataType.DOUBLE:
                    return byteTransform.TransDouble(data, index).ToString();

                case EnumDataType.STRING:
                    return byteTransform.TransString(data, index, lenstr, Encoding.ASCII).ToString();

                default:
                    return string.Empty;
            }
        }

        public OperateResult<bool[]> BatchReadBool()
        {
            if (!isConnected) return new OperateResult<bool[]>() { IsSuccess = false, Content = null };
            var red = ModbusClient.ReadBool(BoolStartAddress.ToString(), (ushort)BoolLen);
            return red;
        }

        public bool ParseBatchBoolData(ref bool[] data, int address)

        {
            if (address + 1 > data.Length) return false;
            return data[address - BoolStartAddress];

        }

        public void UpdateBatchData(List<RegisterConfig> listreg)
        {
            var batch_sorlist =
               listreg.Where(t => !t.DataType.Equals(EnumDataType.BOOL)).
                   OrderBy(t => int.Parse(t.Address)).ToList();

            if (batch_sorlist.Count < 1)
            {
                StartAddress = 0;
                Len = 0;
            }
            else
            {
                var minadd = int.Parse(batch_sorlist.First().Address);
                var maxadd = int.Parse(batch_sorlist.Last().Address);
                StartAddress = minadd;
                Len = maxadd - minadd + GetDataTypeLen(batch_sorlist.Last()) - 1;
            }

            var bool_sorlist =
                listreg.Where(t => t.DataType.Equals(EnumDataType.BOOL)).
                    OrderBy(t => int.Parse(t.Address)).ToList();

            if (bool_sorlist.Count < 1)
            {
                BoolStartAddress = 0;
                BoolLen = 0;
            }
            else
            {
                var minadd_bool = int.Parse(bool_sorlist.First().Address);
                var maxadd_bool = int.Parse(bool_sorlist.Last().Address);
                BoolStartAddress = minadd_bool;
                BoolLen = maxadd_bool - minadd_bool + 1;
            }
        }

        #endregion BatchRead

        #endregion interface

        #region private

        private int GetDataTypeLen(RegisterConfig regdata)
        {
            switch (regdata.DataType)
            {
                case EnumDataType.BOOL:
                    return 1;

                case EnumDataType.SHORT:
                    return 4;

                case EnumDataType.USHORT:
                    return 4;

                case EnumDataType.INT:
                    return 4;

                case EnumDataType.UINT:
                    return 2;

                case EnumDataType.LONG:
                    return 8;

                case EnumDataType.ULONG:
                    return 8;

                case EnumDataType.FLOAT:
                    return 4;

                case EnumDataType.DOUBLE:
                    return 8;

                case EnumDataType.STRING:
                    return regdata.StrLen;

                default:
                    return 2;
            }
        }

        #endregion private
    }
}